import { Function } from '../util'
import { Assert } from './Assert'
import { ObjectTool } from './ObjectTool'

// noinspection JSUnusedGlobalSymbols
/**
 * 数据工具
 * @author 冰凝
 * @date 2022年9月19日 09点50分
 **/
export class ArrayTool {

    /**
     * 数组是空哒? <br>
     * 注意: 非空且非数组类型 永远返回 false {@link isNotEmpty}
     * @param arr 待检查数组
     * @return
     * - 如果 arr 是 null 或 undefined -> true
     * - 如果 arr 是数组 -> arr.length = 0
     * - 如果 arr 是其他类型 -> false
     */
    public static isEmpty(arr: Array<any> | undefined | null): boolean {
        if (!arr) {
            return true
        }
        return Array.isArray(arr) && arr.length === 0
    }

    /**
     * 数组非空? <br>
     * 注意: 非空且非数组类型 永远返回 false {@link isEmpty}
     * @param arr 待检查数组
     * @return
     * - 如果 arr 是 null 或 undefined -> false
     * - 如果 arr 是数组 -> arr.length > 0
     * - 如果 arr 是其他类型 -> false
     */
    public static isNotEmpty(arr: any[] | undefined | null): boolean {
        if (!arr) {
            return false
        }
        return Array.isArray(arr) && arr.length > 0
    }

    // noinspection JSValidateJSDoc
    /**
     * 去重
     * @param sourceArr 源数组
     * @param notNull 是否保留空值, 默认不保留(true) {@link ObjectTool.isNotNull}
     */
    public static distinct<E extends string | number | boolean | null | undefined>(
        sourceArr?: Array<E>,
        notNull: boolean = true,
    ): Array<E> {
        if (ArrayTool.isEmpty(sourceArr)) {
            return []
        }
        return Array.from(new Set(notNull ? sourceArr?.filter(i => ObjectTool.isNotNull(i)) : sourceArr))
    }

    /**
     * 数组 转 Record 对象映射 <br>
     * 如果 keyFun 返回值(最终key)相同, 按照源数组迭代顺序覆盖 <br>
     * 如果需要保留重复的映射, {@link groupBy}
     * @param arr 源数组
     * @param keyFun key 映射
     * @param valueFun value 映射 可选, 默认 为: i => i
     * @return 结果
     */
    public static toRecord<S, K extends PropertyKey, R = S>(
        arr: Array<S> | undefined | null,
        keyFun: Function<S, K>,
        valueFun: Function<S, R> = i => i as any,
    ): Record<K, R> {
        if (this.isEmpty(arr)) {
            return <Record<K, R>>{}
        }
        Assert.isNotNull(keyFun)

        return arr!.reduce((pv, cv) => {
            pv[keyFun(cv)] = valueFun(cv)
            return pv
        }, <Record<K, R>>{})
    }

    /**
     * 数组转 map 映射, 如果 keyFun 返回值相同({@link Map}) 会按迭代顺序覆盖 <br>
     * 如果需要保留重复的映射, {@link groupBy}
     * @param arr 源数组
     * @param keyFun key 映射
     * @param valueFun 值映射
     */
    public static toMap<S, K, R = S>(
        arr: Array<S> | undefined | null,
        keyFun: Function<S, K>,
        valueFun: Function<S, R> = i => i as any,
    ): Map<K, R> {
        if (this.isEmpty(arr)) {
            return new Map<K, R>()
        }
        Assert.isNotNull(keyFun)
        return arr!.reduce((map, current) => {
            map.set(keyFun(current), valueFun(current))
            return map
        }, new Map<K, R>())
    }

    /**
     * 分组
     * @param arr 源数据
     * @param keyFun Key 映射
     * @param valueMapping 可选, 分组后的值映射
     * @see groupMapBy
     */
    public static groupBy<S, K extends PropertyKey, R = S>(
        arr: Array<S>,
        keyFun: (i: S) => K,
        valueMapping: (i: S) => R = i => i as any,
    ): Record<K, Array<R>> {
        if (this.isEmpty(arr)) {
            return <Record<K, Array<R>>>{}
        }
        Assert.isNotNull(keyFun)

        return arr!.reduce((mapping, current) => {
            const key = keyFun(current)
            const value = valueMapping(current)
            if (this.isEmpty(mapping[key])) {
                mapping[key] = [ value ]
            } else {
                mapping[key].push(value)
            }
            return mapping
        }, <Record<K, Array<R>>>{})
    }

    /**
     * 分组
     * @param arr 源数组
     * @param keyFun key 映射
     * @param valueMapping 可选, 分组后的值映射
     * @see groupBy
     */
    public static groupMapBy<S, K, R = S>(
        arr: Array<S>,
        keyFun: (i: S) => K,
        valueMapping: (i: S) => R = i => i as any,
    ): Map<K, Array<R>> {
        if (this.isEmpty(arr)) {
            return new Map<K, Array<R>>()
        }

        Assert.isNotNull(keyFun)

        return arr.reduce((map, current) => {
            const key = keyFun(current)
            const value = valueMapping(current)
            if (this.isEmpty(map.get(key))) {
                map.set(key, [ value ])
            } else {
                map.get(key)!.push(value)
            }

            return map
        }, new Map<K, Array<R>>())
    }

    /**
     * 求和
     */
    public static sum<T>(source: Array<number>): number
    /**
     * 映射后求和
     * @param source 源数组
     * @param map 映射器
     */
    public static sum<T>(source: Array<T>, map: (t: T) => number): number
    public static sum<T>(source: Array<T | number>, map?: (t: T) => number): number {
        if (!map) {
            return (<Array<number>>source).reduce((sum, current) => {
                sum += current
                return sum
            })
        }

        return (<Array<T>>source).reduce((sum, current) => {
            sum += map(current)
            return sum
        }, 0)
    }
}
